package com.sneakxy.cloudbase.platform.controller;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.Calendar;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.csource.common.MyException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.sneakxy.cloudbase.platform.bean.UploadFile;
import com.sneakxy.cloudbase.platform.bean.ValidAdd;
import com.sneakxy.cloudbase.platform.bean.ValidEdit;
import com.sneakxy.cloudbase.platform.bean.dto.UploadFileDTO;
import com.sneakxy.cloudbase.platform.service.IUploadFileService;
import com.sneakxy.cloudbase.platform.shiro.ShiroAccount;
import com.sneakxy.cloudbase.platform.utils.FtpClient;
import com.sneakxy.cloudbase.platform.utils.FtpClientFactory;
import com.sneakxy.cloudbase.platform.utils.fastdfs.FastDfsClient;
import com.sneakxy.cloudbase.platform.utils.web.DataGrid;
import com.sneakxy.cloudbase.platform.utils.web.HttpServletResponseUtils;
import com.sneakxy.cloudbase.platform.utils.web.PageSearch;
import com.sneakxy.cloudbase.platform.utils.web.Response;
import com.sneakxy.cloudbase.platform.utils.web.signature.RequireSignature;
import com.sneakxy.mybatis.commons.repository.query.domain.CriteriaBuilder;
import com.sneakxy.mybatis.commons.utils.MybatisUtils;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import springfox.documentation.annotations.ApiIgnore;

/**
 * 
 * @author 潜行的青衣
 * @see
 */
@Api(tags="文件管理模块服务接口")
@RestController
@RequestMapping("/api/filemanager/file")
public class UploadFileController extends BaseController {
	
	public static final String ROLE_ROOT = "ROLE-ROOT";
	
	public static final String ROLE_ADMIN = "ROLE-ADMIN";
	
	public static final String TOURIST = "tourist";
	
	@Autowired
	private IUploadFileService uploadFileService;
	
	@Autowired
	private FtpClientFactory ftpClientFactory;

	@Autowired
	private FastDfsClient fastDfsClient;
	
	@Value("${local.path}")
	private String localPath;
	
	@Value("${store.type:local}")
	private String defaultStoreType;
	
	@ApiOperation("文件信息分页列表")
	@RequireSignature
	@RequiresPermissions("sys:filemanager:manager")
	@PostMapping("datagrid")
	public Response<DataGrid<UploadFileDTO>> datagrid(PageSearch<String> search, 
			@ApiParam(name="filetype", value="文件种类")
			@RequestParam(name="filetype", required=false)
			String filetype) {
		if(StringUtils.isBlank(search.getSortName())) {
			search.setSortName("uploadtime");
		}
		String searchText = search.getSearch();
		String accountId = getAccountId();
		CriteriaBuilder<UploadFile> builder = new CriteriaBuilder<>(UploadFile.class);
		builder.andEq("owner", accountId);
		if(StringUtils.isNotBlank(searchText)) {
			builder.andLike("name", StringUtils.join("%", searchText, "%"));
		}
		if(StringUtils.isNotBlank(filetype)) {
			builder.andEq("filetype", filetype);
		}
		Page<UploadFile> page = uploadFileService.findAll(builder.build(), search.toPageable());
		return Response.ok(DataGrid.newInstance(page, UploadFileDTO::from));
	}
	
	@ApiImplicitParams({
		@ApiImplicitParam(name="name", value="文件名", dataType="String", dataTypeClass = String.class, paramType="query", required=false),
		@ApiImplicitParam(name="storetype", value="文件存储类型：fdfs、local、ftp", dataType="String", dataTypeClass = String.class, paramType="query", required=false),
		@ApiImplicitParam(name="extent", value="使用范围：0、公共，1、私人", dataType="String", dataTypeClass = String.class, paramType="query", required=true)
	})
	@ApiOperation("添加文件")
	@RequireSignature
	@RequiresPermissions("sys:filemanager:manager:add")
	@PostMapping("add")
	public Response<UploadFileDTO> add(
			@ApiIgnore 
			@Validated(ValidAdd.class)
			UploadFileDTO uploadFile,
			@ApiParam(name="file", value="文件", required=true)
			@RequestParam(name="file")
			MultipartFile file) throws IOException, MyException {
		if(file.isEmpty()) {
			return Response.fail();
		}
		//自动添加拓展名
		uploadFile.setName(fillNameExt(uploadFile.getName(), file.getOriginalFilename()));
		return upload(file, uploadFile.getStoretype(), uploadFile.getExtent(), UploadFileDTO.FILETYPE_NORMAL, uploadFile.getName());
	}
	
	public String fillNameExt(String name, String filename) {
		//自动添加拓展名
		if(StringUtils.isNotBlank(name)) {
			String ext = "";
			int index = StringUtils.lastIndexOf(filename, ".");
			if(index > -1) {
				ext = StringUtils.substring(filename, index);
				if(!StringUtils.endsWith(name, ext)) {
					return StringUtils.join(name, ext);
				}
			}
			return name;
		}
		return filename;
	}
	
	@ApiImplicitParams({
		@ApiImplicitParam(name="id", value="文件ID", dataType="String", dataTypeClass = String.class, paramType="query", required=true),
		@ApiImplicitParam(name="name", value="文件名", dataType="String", dataTypeClass = String.class, paramType="query", required=false),
		@ApiImplicitParam(name="extent", value="使用范围：0、公共，1、私人", dataType="String", dataTypeClass = String.class, paramType="query", required=true)
	})
	@ApiOperation("文件信息修改")
	@RequireSignature
	@RequiresPermissions("sys:filemanager:manager:edit")
	@PostMapping("edit")
	public Response<UploadFileDTO> edit(
			@ApiIgnore 
			@Validated(ValidEdit.class) 
			UploadFileDTO dto) {
		UploadFile old = uploadFileService.findOne(dto.getId());
		if(!StringUtils.equals(getAccountId(), old.getOwner())) {
			return Response.fail("你没有权限修改此文件信息.");
		}
		old.setName(fillNameExt(dto.getName(), old.getName()));
		old.setExtent(dto.getExtent());
		old = uploadFileService.save(old);
		
		return Response.ok(UploadFileDTO.from(old));
	}
	
	@ApiOperation("文件信息删除")
	@RequireSignature
	@RequiresPermissions("sys:filemanager:manager:remove")
	@PostMapping("remove")
	public Response<String> remove(
			@ApiParam(name="ids", value="文件ID集合", required = true)
			@RequestParam(name="ids") 
			String[] ids) throws IOException, MyException {
		if(ids != null) {
			UploadFile file = null;
			ShiroAccount account = getLoginAccount();
			boolean isNotAdmin = !isAdmin();
			for(String id:ids) {
				file = uploadFileService.findOne(id);
				if(isNotAdmin && !StringUtils.equals(account.getId(), file.getOwner())) {
					continue;
				}
				uploadFileService.remove(id);
			}
		}
		return Response.ok();
	}
	
	@ApiOperation("文件下载")
	@RequestMapping(value = "download", method = { RequestMethod.GET, RequestMethod.POST })
	public void download(
			HttpServletRequest request,
			HttpServletResponse response,
			@ApiParam(name="id", value="文件ID", required = true)
			@RequestParam(name="id") 
			String id) throws IOException, MyException {
		String tableName = uploadFileService.getTableName(id);
		UploadFile file = this.uploadFileService.findOne(id, tableName);
		//私人文件
		if(StringUtils.equals(file.getExtent(), UploadFileDTO.EXTENT_PRIVATE)) {
			ShiroAccount account = getLoginAccount();
			if(account != null && !StringUtils.equals(account.getId(), file.getOwner())) {
				throw new UnauthorizedException("您没有权限下载此文件.");
			}
		}
		//下载次数+1
		file.setDownloads(file.getDownloads() + 1);
		uploadFileService.save(file, tableName);
		//下载操作
		response.setContentType(file.getContentType());
		response.addHeader("Content-Disposition", "attachment;filename=" + java.net.URLEncoder.encode(file.getName(), "UTF-8"));
		if(StringUtils.equals(UploadFileDTO.TYPE_FTP, file.getStoretype())) {
			FtpClient ftpClient = ftpClientFactory.getObject();
			try {
				ftpClient.login();
				ftpClient.download(request, response, file.getPath(), file.getSize());
			} finally {
				ftpClient.close();
			}
		} else if(StringUtils.equals(UploadFileDTO.TYPE_FASTDFS, file.getStoretype())) {
			this.fastDfsClient.downloadFile(request, response, file.getPath(), file.getSize());
		} else if(StringUtils.equals(UploadFileDTO.TYPE_LOCAL, file.getStoretype())) {
			String basePath = StringUtils.endsWith(localPath, File.separator) ? localPath : StringUtils.join(localPath, File.separator);
			String path = StringUtils.join(basePath, file.getPath());
			HttpServletResponseUtils.download(request, response, new FileInputStream(new File(path)), file.getSize());
		}
	}
	
	@ApiOperation("查询单个文件信息")
	@RequireSignature
	@PostMapping("one")
	public Response<UploadFileDTO> one(
			@ApiParam(name="id", value="文件ID", required = true)
			@RequestParam(name="id", required=true) 
			String id) {
		return Response.ok(uploadFileService.findOne(id), UploadFileDTO::from);
	}
	
	@ApiOperation("文件上传")
	@RequireSignature
	@RequiresPermissions("sys:filemanager:manager:add")
	@PostMapping("upload")
	public Response<UploadFileDTO> upload(
			@ApiParam(name="file", value="文件")
			@RequestPart("file") 
			MultipartFile file,
			@ApiParam(name="storetype", value="文件类型:fastdfs、ftp、local")
			@RequestParam(name="storetype", required = false) 
			String storetype,
			@ApiParam(name="extent", value="文件分类：0、公用，1、私有", required = true)
			@RequestParam(name="extent") 
			String extent,
			@ApiParam(name="filetype", value="文件种类：0、普通文件，1~：其他自定义类型", required = true)
			@RequestParam(name="filetype") 
			String filetype,
			@ApiParam(name="name", value="自定义文件名", required = false)
			@RequestParam(name="name", required = false) 
			String name) throws IOException, MyException {
		if(file.isEmpty()) {
			return Response.fail();
		}
		String accountId = getAccountId();
		String storeType = hasRole(ROLE_ROOT) && StringUtils.isNotBlank(storetype) ? storetype : defaultStoreType; 
		String path = null;
		if(StringUtils.equals(UploadFileDTO.TYPE_FTP, storeType)) {
			path = uploadFileService.storeToFtp(file, accountId);
		} else if(StringUtils.equals(UploadFileDTO.TYPE_FASTDFS, storeType)) {
			path = uploadFileService.storeToFastDfs(file);
		} else if(StringUtils.equals(UploadFileDTO.TYPE_LOCAL, storeType)) {
			path = uploadFileService.storeToLocal(file, accountId);
		}
		if(StringUtils.isNotBlank(path)) {
			UploadFile uFile = new UploadFile();
			uFile.setId(MybatisUtils.getUUID());
			uFile.setContentType(file.getContentType());
			uFile.setStoretype(storeType);
			uFile.setDownloads(0L);
			uFile.setExtent(extent);
			uFile.setFiletype(filetype);
			uFile.setNew(true);
			if(StringUtils.isNotBlank(name)) {
				uFile.setName(name);
			} else {
				uFile.setName(file.getOriginalFilename());
			}
			uFile.setOwner(accountId);
			uFile.setPath(path);
			uFile.setSize(file.getSize());
			uFile.setUploadtime(DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss"));
			uploadFileService.save(uFile);
			return Response.ok(UploadFileDTO.from(uFile));
		}
		return Response.fail("未知存储类型.");
	}
	
	@ApiOperation("图片BASE64码保存")
	@RequireSignature
	@RequiresPermissions("sys:filemanager:manager:add")
	@PostMapping("base64")
	public Response<UploadFileDTO> base64(
			@ApiParam(name="source", value="图片BASE64码字符串", required = true)
			@RequestParam(name="source") 
			String source,
			@ApiParam(name="storetype", value="文件类型:fastdfs、ftp、local")
			@RequestParam(name="storetype", required = false) 
			String storetype,
			@ApiParam(name="extent", value="文件分类：0、公用，1、私有", required = true)
			@RequestParam("extent") 
			String extent,
			@ApiParam(name="filetype", value="文件种类：0、普通文件，1~：其他自定义类型", required = true)
			@RequestParam(name="filetype") 
			String filetype,
			@ApiParam(name="name", value="自定义文件名", required = false)
			@RequestParam(name="name") 
			String name) throws IOException, MyException {
		String path = null;
		String accountId = getAccountId();
		//图片处理
		Base64.Decoder decoder = Base64.getDecoder();
		byte[] buffer = decoder.decode(source);
		ByteArrayInputStream imageIO = new ByteArrayInputStream(buffer);
		String storeType = hasRole(ROLE_ROOT) && StringUtils.isNotBlank(storetype) ? storetype : defaultStoreType; 
		if(StringUtils.equals(UploadFileDTO.TYPE_FTP, storeType)) {
			path = uploadFileService.storeToFtp(imageIO, accountId);
		} else if(StringUtils.equals(UploadFileDTO.TYPE_FASTDFS, storeType)) {
			path = uploadFileService.storeToFastDfs(imageIO, buffer.length);
		} else if(StringUtils.equals(UploadFileDTO.TYPE_LOCAL, storeType)) {
			path = uploadFileService.storeToLocal(imageIO, accountId);
		}
		if(StringUtils.isNotBlank(path)) {
			UploadFile uFile = new UploadFile();
			uFile.setId(MybatisUtils.getUUID());
			uFile.setContentType("image/jpeg");
			uFile.setStoretype(storeType);
			uFile.setDownloads(0L);
			uFile.setExtent(extent);
			uFile.setFiletype(filetype);
			uFile.setNew(true);
			uFile.setName(name);
			uFile.setOwner(accountId);
			uFile.setPath(path);
			uFile.setSize((long) buffer.length);
			uFile.setUploadtime(DateFormatUtils.format(Calendar.getInstance(), "yyyy-MM-dd HH:mm:ss"));
			uploadFileService.save(uFile);
			return Response.ok(UploadFileDTO.from(uFile));
		}
		return Response.fail();
	}
	
	protected boolean isRoot() {
		return hasRole(ROLE_ROOT);
	}
	
	protected boolean isAdmin() {
		return hasRole(ROLE_ADMIN) || isRoot();
	}
	
	protected String getAccountId() {
		ShiroAccount account = getLoginAccount();
		return getAccountId(account);
	}
	
	protected String getAccountId(ShiroAccount account) {
		return account != null ? account.getId() : TOURIST;
	}
	
}
